package com.songlanyun.modules.account.controller.api;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.songlanyun.common.enums.MemberConstant;
import com.songlanyun.common.exception.RRException;
import com.songlanyun.common.utils.R;
import com.songlanyun.common.utils.RedisKeys;
import com.songlanyun.common.utils.RedisUtils;
import com.songlanyun.common.validator.ValidatorUtils;
import com.songlanyun.common.validator.group.AppGroup;
import com.songlanyun.common.validator.group.AppLoginGroup;
import com.songlanyun.common.validator.group.WxGroup;
import com.songlanyun.common.validator.group.WxLoginGroup;
import com.songlanyun.modules.account.entity.AccountEntity;
import com.songlanyun.modules.account.model.dto.AccountDTO;
import com.songlanyun.modules.account.service.AccountService;
import com.songlanyun.modules.account.service.CommonService;
import com.songlanyun.modules.appconfig.entity.AppConfig;
import com.songlanyun.modules.appconfig.service.AppConfigService;
import com.songlanyun.modules.chain.service.ChainService;
import com.songlanyun.modules.exception.MemberException;
import com.songlanyun.modules.rabbitmq.MqConstant;
import com.songlanyun.modules.rabbitmq.service.SendMsgService;
import com.songlanyun.modules.shop.entity.ShopEntity;
import com.songlanyun.modules.shop.service.ShopService;
import com.songlanyun.modules.user.exception.UserException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import java.util.HashMap;
import java.util.Map;

/**
 * 会员注册登录接口
 */

@RestController
@RequestMapping("api/account")
@Slf4j
public class AccountLoginApiController {

    @Resource
    private AccountService accountService;
    @Resource
    private CommonService commonService;
    @Resource
    private ShopService shopService;

    @Resource
    private SendMsgService sendMsgService;

    @Resource
    RedisUtils redisUtils;

    @Resource
    ChainService chainService;


    @Value("${version.link.address}")
    private String versionLinkAddress;

    @Autowired
    private AppConfigService appConfigService;

    @PermitAll
    @GetMapping("/versionLinkAddress")
    public R getVersionLinkAddress() {
        return R.ok().put("data", versionLinkAddress);
    }

    //********* APP **********

    /**
     * APP会员注册
     */
    @PostMapping("/app/register")
    public R appRegister(@RequestBody AccountDTO dto) {
        //校验
        ValidatorUtils.validateEntity(dto, AppGroup.class);

        //检查验证码是否正确
        commonService.checkVerCode(dto.getMobile(), dto.getCaptcha());

        //判断手机号是否注册过
        AccountEntity acountM = accountService.getByMobile(dto.getMobile());
        if (ObjectUtil.isNotNull(acountM)) {

            //手机号已注册
            return R.error(MemberException.PHONE_IS_REGISTER.getCode(), MemberException.PHONE_IS_REGISTER.getMsg());
        }

        //根据邀请码查询用户
        String invitationCode = dto.getInvitationCode();
        AccountEntity invitationAccount = accountService.disposeCode(invitationCode);

        //保存会员信息
        dto.setRegisterSource(MemberConstant.RegisterSource.APP);//会员注册来源
        Long userId = accountService.saveAccount(invitationAccount, dto);

        // 获取会员区块链地址并更新会员信息
        try {
            String accountAddress = chainService.createAccountAddress(userId);
            accountService.updateAccountAddress(userId, accountAddress);
        } catch (Exception e){
            log.error("userId: {} app注册成功,更新区块链地址失败", userId, e);
        }

        //赠送新人券 发送mq消息
        sendMsgService.sendCommon(MqConstant.UserRegSuccessConstant.USER_REG_SUCCESS_EXCHANGE,
                MqConstant.UserRegSuccessConstant.USER_REG_SUCCESS_ROUTER
                , String.valueOf(userId));
        return R.ok();
    }


    /**
     * APP密码登录
     */
    @PostMapping("/app/loginPassword")
    public R loginPassword(@RequestBody AccountDTO accountDTO) {

        AppConfig appConfig = appConfigService.getOne(new LambdaQueryWrapper<AppConfig>().orderByDesc(AppConfig::getId)
                .last("LIMIT 1"));
        String ver = appConfig.getVer();
        //校验版本
        if (accountDTO.getType() == null){
            throw new RRException("请更新最新版本登录");
        }
        //传过来的版本号
        String[] split = accountDTO.getVersion().split("\\."); // 使用双斜杠来转义点号
        String joinedString = String.join("", split);
        Integer integerVersion = Integer.valueOf(joinedString);
        //数据库的版本号
        String[] verSplit = ver.split("\\."); // 使用双斜杠来转义点号
        String verJoinedString = String.join("", verSplit);
        Integer verIntegerVersion = Integer.valueOf(verJoinedString);

        if (accountDTO.getType().equals("0") && integerVersion < verIntegerVersion){
            throw new RRException("请更新最新版本登录");
        }



        //校验数据
        ValidatorUtils.validateEntity(accountDTO, AppLoginGroup.class);

        //获取用户信息
        AccountEntity account = accountService.getByMobile(accountDTO.getMobile());

        //校验密码
        if (ObjectUtil.isNull(account) || !account.getPassword().equals(new Sha256Hash(accountDTO.getPassword(), account.getSalt()).toHex())) {
            return R.error(MemberException.LOGIN_WRONG.getMsg());
        }

        //设置最后一次登录时间
        accountService.lastLoginTime(account.getId());
        //登录成功调用
        logoSuccess(account.getId(), accountDTO.getCid());
        return R.ok().put("data", result(account.getId()));
    }

    /**
     * APP验证码登录
     */
    @PostMapping("/app/loginCaptcha")
    public R loginCaptcha(@RequestBody AccountDTO accountDTO) {
        AppConfig appConfig = appConfigService.getOne(new LambdaQueryWrapper<AppConfig>().orderByDesc(AppConfig::getId)
                .last("LIMIT 1"));
        String ver = appConfig.getVer();
        //校验版本
        if (accountDTO.getType() == null){
            throw new RRException("请更新最新版本登录");
        }
        //传过来的版本号
        String[] split = accountDTO.getVersion().split("\\."); // 使用双斜杠来转义点号
        String joinedString = String.join("", split);
        Integer integerVersion = Integer.valueOf(joinedString);
        //数据库的版本号
        String[] verSplit = ver.split("\\."); // 使用双斜杠来转义点号
        String verJoinedString = String.join("", verSplit);
        Integer verIntegerVersion = Integer.valueOf(verJoinedString);

        if (accountDTO.getType().equals("0") && integerVersion < verIntegerVersion){
            throw new RRException("请更新最新版本登录");
        }
        //校验数据
        ValidatorUtils.validateEntity(accountDTO, WxLoginGroup.class);
        //校验验证码
        commonService.checkVerCode(accountDTO.getMobile(), accountDTO.getCaptcha());

        //获取用户信息
        AccountEntity account = accountService.getByMobile(accountDTO.getMobile());
        if (account == null) {
            return R.error(MemberException.MOBILE_NOT_REGISTER.getMsg());
        }
        //设置最后一次登录时间
        accountService.lastLoginTime(account.getId());
        //登录成功调用
        logoSuccess(account.getId(), accountDTO.getCid());
        return R.ok().put("data", result(account.getId()));
    }


    //*******************

    //********* 小程序 **********

    /**
     * 获取小程序OpenId
     *
     * @param params 输入参数，必须包含的key为code，代表了微信小程序的Authentication Code。
     * @return R的实例，包含了个key：openId，代表了微信小程序的openId。
     */
    @PostMapping("/wx/authorization")
    public R authorization(@RequestBody Map<String, Object> params) {
        String openId = accountService.getOpenId(params);

        if (StringUtils.isBlank(openId)) {
            return R.error("小程序授权登录失败");
        }

        //查询此openid是否绑定过用户
        AccountEntity account = accountService.getAccountByOpenId(openId);

        //该用户未绑定过，则需要去注册
        if (account == null) {
            return R.ok().put("data", result(false, null, openId));
        }

        //用户已锁定，不能登录
        isLock(account.getLockStatus());
        //设置最后一次登录时间
        accountService.lastLoginTime(account.getId());
        //已注册
        return R.ok().put("data", result(true, account.getId(), null));

    }

    /**
     * 获取小程序OpenId和SessionKey
     *
     * @param params 输入参数，必须包含的key为code，代表了微信小程序的Authentication Code。
     * @return R的实例，包含了2个key：openId和sessionKey，分别代表了微信小程序的openId和SessionKey。
     */
    @PostMapping("/wx/IdAndSessionKey")
    public R getOpenIdAndSessionKey(@RequestBody Map<String, Object> params) {
        Map<String, String> idAndSessionKey = accountService.getOpenIdAndSessionKey(params);
        return R.ok().put("data", idAndSessionKey);
    }

    /**
     * 获取小程序的手机号
     *
     * @param params 输入参数，必须包含的key为openId，代表了微信小程序的openId。
     * @return R的实例，包含了个key：mobile，分别代表了微信当前用户的手机号。
     */
    @PostMapping("/wx/authMobile")
    public R authMobile(@RequestBody Map<String, Object> params) {
        String mobile = accountService.getWxMobile(params);
        HashMap<String, String> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("mobile", mobile);
        return R.ok().put("data", objectObjectHashMap);
    }

    /**
     * 小程序注册
     *
     * @param accountDTO <textarea>输入参数，各个字段含义如下：
     * <ul>invitation_ode：注册时的邀请码</ul>
     * <ul>mobile：注册时的手机号</ul>
     * <ul>open_id：小程序中当前用户的openId</ul>
     * <ul>register_source：会员注册来源——0：App，1：微信</ul>
     * <ul>cid：UniPush的客户端ID，这个id定位了你的手机</ul>
     * <ul>version：版本号</ul>
     * <ul>type：类型区分——0：App，1：小程序</ul>
     * </textarea>
     *
     * @return R的实例，包含了2个key：token和shop，分别代表了用户登录系统后的token和用户所属的店铺对象。
     **/
    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/wx/register")
    public R WxRegister(@RequestBody AccountDTO accountDTO) {
        //校验
        ValidatorUtils.validateEntity(accountDTO, WxGroup.class);
        //openId
        String openId = accountDTO.getOpenId();
        String mobile = accountDTO.getMobile();
//        //检查验证码是否正确 暂不验证
//        commonService.checkVerCode(mobile, accountDTO.getCaptcha());

        //查询此openid是否绑定过用户
        AccountEntity account = accountService.getAccountByOpenId(openId);

        if (account != null) {
            //用户已锁定，不能登录
            isLock(account.getLockStatus());
            //该用户已绑定过，则无需绑定
            return R.error(MemberException.PHONE_IS_BINDING.getCode(), MemberException.PHONE_IS_BINDING.getMsg());
        }
        //根据手机号查询会员账号信息
        AccountEntity accountM = accountService.getByMobile(mobile);
        //微信注册
        Long userId = accountService.WxRegister(accountDTO, accountM);

        // 获取会员区块链地址并更新会员信息
        try {
            String accountAddress = chainService.createAccountAddress(userId);
            accountService.updateAccountAddress(userId, accountAddress);
        } catch (Exception e){
            log.error("userId: {} 微信注册成功,更新区块链地址失败", userId, e);
        }

        //赠送新人券 发送mq消息
        sendMsgService.sendCommon(MqConstant.UserRegSuccessConstant.USER_REG_SUCCESS_EXCHANGE,
                MqConstant.UserRegSuccessConstant.USER_REG_SUCCESS_ROUTER
                , String.valueOf(userId));

        //////////////////////////////////// 立即实现自动登录 ////////////////////////////////////////////

        //用户已锁定，不能登录
        accountM = accountService.getByMobile(mobile);
        isLock(accountM.getLockStatus());

        //设置最后一次登录时间
        accountService.lastLoginTime(accountM.getId());

        //登录成功调用
        logoSuccess(accountM.getId(), accountDTO.getCid());

        return R.ok().put("data", result(accountM.getId()));
    }

    /**
     * 小程序登录
     *
     * @param accountDTO <textarea>输入参数，各个字段含义如下：
     * <ul>mobile：注册时的手机号</ul>
     * <ul>version：版本号</ul>
     * <ul>type：类型区分——0：App，1：小程序</ul>
     * </textarea>
     *
     * @return R的实例，包含了2个key：token和shop，分别代表了用户登录系统后的token和用户所属的店铺对象。
     */
    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/wx/login")
    public R wxLogin(@RequestBody AccountDTO accountDTO) {

        //获取用户信息
        AccountEntity account = accountService.getByMobile(accountDTO.getMobile());

        if(account == null){
            throw new RRException(UserException.MOBILE_NOT_REGISTERED_FOR_WECHAT);
        } else {
            //用户已锁定，不能登录
            isLock(account.getLockStatus());
        }

        //设置最后一次登录时间
        accountService.lastLoginTime(account.getId());

        //登录成功调用
        logoSuccess(account.getId(), accountDTO.getCid());

        return R.ok().put("data", result(account.getId()));
    }

    /**
     * 小程序根据openid得到会员信息
     *
     * @param openId 输入参数，代表了微信小程序的openId。
     * @return R的实例，代表了会员对象，各个字段含义如下：
     * <textarea>
     * <ul>id：会员的ID</ul>
     * <ul>mobile：会员的手机号</ul>
     * <ul>password：会员密码的密文</ul>
     * <ul>salt：会员密码在加密时用到的随机值</ul>
     * <ul>lockStatus：会员的锁定状态</ul>
     * <ul>lastLoginTime：最后一次登录时间</ul>
     * <ul>createTime：会员在系统中的创建时间</ul>
     * <ul>updateTime：会员在系统中的更新时间</ul>
     * <ul>parentUserId：推荐用户Id(父级id)</ul>
     * <ul>subjectionShopId：隶属那个商家Id</ul>
     * <ul>invitationCode：邀请码</ul>
     * <ul>qrcode：二维码地址</ul>
     * <ul>registerSource：会员注册来源——0：App，1：微信</ul>
     * <ul>registerTime：注册时间</ul>
     * <ul>parentPath：上级推荐人id逗号拼接</ul>
     * <ul>isCelebrity：是否是网红</ul>
     * <ul>activating：是否被激活</ul>
     * <ul>lastActivatingTime：最后被激活的时间</ul>
     * <ul>grade：会员等级</ul>
     * </textarea>
     **/
    @GetMapping("/wx/getWxUserInfo")
    public R getWxUserInfo(@RequestParam("open_id") String openId) {
        // 查询会员信息
        AccountEntity account = accountService.getAccountByOpenId(openId);

        if(account == null) {
            return R.ok().put("data", result(false, null, openId));
        }

        // 脱敏处理
        account.setMobile(null);
        account.setPassword(null);
        account.setSalt(null);
        account.setParentUserId(null);
        account.setParentPath(null);

        // 返回结果
        return R.ok().put("data", account);
    }

    /**
     * 设置登录返回值
     *
     * @param id
     * @return
     */
    private Map<String, Object> result(Long id) {
        //会员的隶属商家
        ShopEntity shop = shopService.getSubShopById(id, false);
        //生成token，存入redis
        String token = commonService.createToken(id);
        Map<String, Object> result = new HashMap<>();
        result.put("token", token);
        result.put("shop", shop);
        return result;
    }

    /**
     * 设置注册返回值
     *
     * @param isReg  是否注册
     * @param id
     * @param openId
     * @return
     */
    private Map<String, Object> result(boolean isReg, Long id, String openId) {
        Map<String, Object> result = new HashMap<>();
        //已注册
        result.put("is_reg", isReg);
        //已注册
        if (isReg) {
            //会员的隶属商家
            ShopEntity shop = shopService.getSubShopById(id, false);
            //有，代表token过期，需要重新生成一个token
            String token = commonService.createToken(id);
            result.put("token", token);
            result.put("shop", shop);
        } else {
            result.put("open_id", openId);
        }

        return result;
    }


    /**
     * 用户锁定
     *
     * @param LockStatus
     */
    private void isLock(boolean LockStatus) {
        if (LockStatus) {
            throw new RRException(MemberException.USER_DISABLED);
        }
    }

    private void logoSuccess(Long userId, String cid) {
        //将UniPush的客户端ID存入缓存
        log.info(String.format("个推客户端ID：%s", cid));
        if (ObjectUtil.isNull(userId) || StringUtils.isBlank(cid)) {
            return;
        }
        redisUtils.set(RedisKeys.getPushCid(String.valueOf(userId)), cid);
    }

}
